OLD | NEW |
| (Empty) |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 import base64 | |
6 import httplib2 | |
7 import json | |
8 import os | |
9 import tempfile | |
10 import unittest | |
11 | |
12 from googleapiclient import errors | |
13 import mock | |
14 | |
15 from infra_libs import httplib2_utils | |
16 from infra_libs.ts_mon.common import interface | |
17 from infra_libs.ts_mon.common import monitors | |
18 from infra_libs.ts_mon.common import pb_to_popo | |
19 from infra_libs.ts_mon.common import targets | |
20 from infra_libs.ts_mon.protos import metrics_pb2 | |
21 import infra_libs | |
22 | |
23 | |
24 class MonitorTest(unittest.TestCase): | |
25 | |
26 def test_send(self): | |
27 m = monitors.Monitor() | |
28 metric1 = metrics_pb2.MetricsData(name='m1') | |
29 with self.assertRaises(NotImplementedError): | |
30 m.send(metric1) | |
31 | |
32 class HttpsMonitorTest(unittest.TestCase): | |
33 | |
34 def setUp(self): | |
35 super(HttpsMonitorTest, self).setUp() | |
36 | |
37 def message(self, pb): | |
38 pb = monitors.Monitor._wrap_proto(pb) | |
39 return json.dumps({'resource': pb_to_popo.convert(pb) }) | |
40 | |
41 def _test_send(self, http): | |
42 mon = monitors.HttpsMonitor('endpoint', ':gce', http=http) | |
43 resp = mock.MagicMock(spec=httplib2.Response, status=200) | |
44 mon._http.request = mock.MagicMock(return_value=[resp, ""]) | |
45 | |
46 metric1 = metrics_pb2.MetricsData(name='m1') | |
47 mon.send(metric1) | |
48 metric2 = metrics_pb2.MetricsData(name='m2') | |
49 mon.send([metric1, metric2]) | |
50 collection = metrics_pb2.MetricsCollection(data=[metric1, metric2]) | |
51 mon.send(collection) | |
52 | |
53 mon._http.request.assert_has_calls([ | |
54 mock.call('endpoint', method='POST', body=self.message(metric1)), | |
55 mock.call('endpoint', method='POST', | |
56 body=self.message([metric1, metric2])), | |
57 mock.call('endpoint', method='POST', body=self.message(collection)), | |
58 ]) | |
59 | |
60 def test_default_send(self): | |
61 self._test_send(None) | |
62 | |
63 def test_http_send(self): | |
64 self._test_send(httplib2.Http()) | |
65 | |
66 def test_instrumented_http_send(self): | |
67 self._test_send(httplib2_utils.InstrumentedHttp('test')) | |
68 | |
69 @mock.patch('infra_libs.ts_mon.common.monitors.HttpsMonitor.' | |
70 '_load_credentials', autospec=True) | |
71 def test_send_resp_failure(self, _load_creds): | |
72 mon = monitors.HttpsMonitor('endpoint', '/path/to/creds.p8.json') | |
73 resp = mock.MagicMock(spec=httplib2.Response, status=400) | |
74 mon._http.request = mock.MagicMock(return_value=[resp, ""]) | |
75 | |
76 metric1 = metrics_pb2.MetricsData(name='m1') | |
77 mon.send(metric1) | |
78 | |
79 mon._http.request.assert_called_once_with('endpoint', method='POST', | |
80 body=self.message(metric1)) | |
81 | |
82 @mock.patch('infra_libs.ts_mon.common.monitors.HttpsMonitor.' | |
83 '_load_credentials', autospec=True) | |
84 def test_send_http_failure(self, _load_creds): | |
85 mon = monitors.HttpsMonitor('endpoint', '/path/to/creds.p8.json') | |
86 mon._http.request = mock.MagicMock(side_effect=ValueError()) | |
87 | |
88 metric1 = metrics_pb2.MetricsData(name='m1') | |
89 mon.send(metric1) | |
90 | |
91 mon._http.request.assert_called_once_with('endpoint', method='POST', | |
92 body=self.message(metric1)) | |
93 | |
94 | |
95 class PubSubMonitorTest(unittest.TestCase): | |
96 | |
97 def setUp(self): | |
98 super(PubSubMonitorTest, self).setUp() | |
99 interface.state.target = targets.TaskTarget( | |
100 'test_service', 'test_job', 'test_region', 'test_host') | |
101 | |
102 @mock.patch('infra_libs.httplib2_utils.InstrumentedHttp', autospec=True) | |
103 @mock.patch('infra_libs.ts_mon.common.monitors.discovery', autospec=True) | |
104 @mock.patch('infra_libs.ts_mon.common.monitors.GoogleCredentials', | |
105 autospec=True) | |
106 def test_init_service_account(self, gc, discovery, instrumented_http): | |
107 m_open = mock.mock_open(read_data='{"type": "service_account"}') | |
108 creds = gc.from_stream.return_value | |
109 scoped_creds = creds.create_scoped.return_value | |
110 http_mock = instrumented_http.return_value | |
111 metric1 = metrics_pb2.MetricsData(name='m1') | |
112 with mock.patch('infra_libs.ts_mon.common.monitors.open', m_open, | |
113 create=True): | |
114 mon = monitors.PubSubMonitor('/path/to/creds.p8.json', 'myproject', | |
115 'mytopic') | |
116 mon.send(metric1) | |
117 | |
118 m_open.assert_called_once_with('/path/to/creds.p8.json', 'r') | |
119 creds.create_scoped.assert_called_once_with(monitors.PubSubMonitor._SCOPES) | |
120 scoped_creds.authorize.assert_called_once_with(http_mock) | |
121 discovery.build.assert_called_once_with('pubsub', 'v1', http=http_mock) | |
122 self.assertEquals(mon._topic, 'projects/myproject/topics/mytopic') | |
123 | |
124 @mock.patch('infra_libs.httplib2_utils.InstrumentedHttp', autospec=True) | |
125 @mock.patch('infra_libs.ts_mon.common.monitors.discovery', autospec=True) | |
126 @mock.patch('infra_libs.ts_mon.common.monitors.gce.AppAssertionCredentials', | |
127 autospec=True) | |
128 def test_init_gce_credential(self, aac, discovery, instrumented_http): | |
129 creds = aac.return_value | |
130 http_mock = instrumented_http.return_value | |
131 metric1 = metrics_pb2.MetricsData(name='m1') | |
132 mon = monitors.PubSubMonitor(':gce', 'myproject', 'mytopic') | |
133 mon.send(metric1) | |
134 | |
135 aac.assert_called_once_with(monitors.PubSubMonitor._SCOPES) | |
136 creds.authorize.assert_called_once_with(http_mock) | |
137 discovery.build.assert_called_once_with('pubsub', 'v1', http=http_mock) | |
138 self.assertEquals(mon._topic, 'projects/myproject/topics/mytopic') | |
139 | |
140 @mock.patch('infra_libs.httplib2_utils.InstrumentedHttp', autospec=True) | |
141 @mock.patch('infra_libs.ts_mon.common.monitors.discovery', autospec=True) | |
142 @mock.patch('infra_libs.ts_mon.common.monitors.Storage', autospec=True) | |
143 def test_init_storage(self, storage, discovery, instrumented_http): | |
144 storage_inst = mock.Mock() | |
145 storage.return_value = storage_inst | |
146 creds = storage_inst.get.return_value | |
147 | |
148 m_open = mock.mock_open(read_data='{}') | |
149 http_mock = instrumented_http.return_value | |
150 metric1 = metrics_pb2.MetricsData(name='m1') | |
151 with mock.patch('infra_libs.ts_mon.common.monitors.open', m_open, | |
152 create=True): | |
153 mon = monitors.PubSubMonitor('/path/to/creds.p8.json', 'myproject', | |
154 'mytopic') | |
155 mon.send(metric1) | |
156 | |
157 m_open.assert_called_once_with('/path/to/creds.p8.json', 'r') | |
158 storage_inst.get.assert_called_once_with() | |
159 creds.authorize.assert_called_once_with(http_mock) | |
160 discovery.build.assert_called_once_with('pubsub', 'v1', http=http_mock) | |
161 self.assertEquals(mon._topic, 'projects/myproject/topics/mytopic') | |
162 | |
163 @mock.patch('infra_libs.ts_mon.common.monitors.PubSubMonitor.' | |
164 '_load_credentials', autospec=True) | |
165 @mock.patch('googleapiclient.discovery.build', autospec=True) | |
166 def test_send(self, _discovery, _load_creds): | |
167 mon = monitors.PubSubMonitor('/path/to/creds.p8.json', 'myproject', | |
168 'mytopic') | |
169 mon._api = mock.MagicMock() | |
170 topic = 'projects/myproject/topics/mytopic' | |
171 | |
172 metric1 = metrics_pb2.MetricsData(name='m1') | |
173 mon.send(metric1) | |
174 metric2 = metrics_pb2.MetricsData(name='m2') | |
175 mon.send([metric1, metric2]) | |
176 collection = metrics_pb2.MetricsCollection(data=[metric1, metric2]) | |
177 mon.send(collection) | |
178 | |
179 def message(pb): | |
180 pb = monitors.Monitor._wrap_proto(pb) | |
181 return {'messages': [{'data': base64.b64encode(pb.SerializeToString())}]} | |
182 publish = mon._api.projects.return_value.topics.return_value.publish | |
183 publish.assert_has_calls([ | |
184 mock.call(topic=topic, body=message(metric1)), | |
185 mock.call().execute(num_retries=5), | |
186 mock.call(topic=topic, body=message([metric1, metric2])), | |
187 mock.call().execute(num_retries=5), | |
188 mock.call(topic=topic, body=message(collection)), | |
189 mock.call().execute(num_retries=5), | |
190 ]) | |
191 | |
192 @mock.patch('infra_libs.ts_mon.common.monitors.PubSubMonitor.' | |
193 '_load_credentials', autospec=True) | |
194 @mock.patch('googleapiclient.discovery.build', autospec=True) | |
195 def test_send_uninitialized(self, discovery, _load_creds): | |
196 """Test initialization retry logic, and also un-instrumented http path.""" | |
197 discovery.side_effect = EnvironmentError() # Fail initialization. | |
198 mon = monitors.PubSubMonitor('/path/to/creds.p8.json', 'myproject', | |
199 'mytopic', use_instrumented_http=False) | |
200 | |
201 metric1 = metrics_pb2.MetricsData(name='m1') | |
202 mon.send(metric1) | |
203 self.assertIsNone(mon._api) | |
204 | |
205 # Another retry: initialization succeeds. | |
206 discovery.side_effect = None | |
207 mon.send(metric1) | |
208 | |
209 def message(pb): | |
210 pb = monitors.Monitor._wrap_proto(pb) | |
211 return {'messages': [{'data': base64.b64encode(pb.SerializeToString())}]} | |
212 | |
213 topic = 'projects/myproject/topics/mytopic' | |
214 | |
215 publish = mon._api.projects.return_value.topics.return_value.publish | |
216 publish.assert_has_calls([ | |
217 mock.call(topic=topic, body=message(metric1)), | |
218 mock.call().execute(num_retries=5), | |
219 ]) | |
220 | |
221 @mock.patch('infra_libs.ts_mon.common.monitors.PubSubMonitor.' | |
222 '_load_credentials', autospec=True) | |
223 @mock.patch('googleapiclient.discovery.build', autospec=True) | |
224 def test_send_fails(self, _discovery, _load_creds): | |
225 # Test for an occasional flake of .publish().execute(). | |
226 mon = monitors.PubSubMonitor('/path/to/creds.p8.json', 'myproject', | |
227 'mytopic') | |
228 mon._api = mock.MagicMock() | |
229 topic = 'projects/myproject/topics/mytopic' | |
230 | |
231 metric1 = metrics_pb2.MetricsData(name='m1') | |
232 mon.send(metric1) | |
233 | |
234 publish = mon._api.projects.return_value.topics.return_value.publish | |
235 publish.side_effect = ValueError() | |
236 | |
237 metric2 = metrics_pb2.MetricsData(name='m2') | |
238 mon.send([metric1, metric2]) | |
239 collection = metrics_pb2.MetricsCollection(data=[metric1, metric2]) | |
240 publish.side_effect = errors.HttpError( | |
241 mock.Mock(status=404, reason='test'), '') | |
242 mon.send(collection) | |
243 | |
244 # Test that all caught exceptions are specified without errors. | |
245 # When multiple exceptions are specified in the 'except' clause, | |
246 # they are evaluated lazily, and may contain syntax errors. | |
247 # Throwing an uncaught exception forces all exception specs to be | |
248 # evaluated, catching more runtime errors. | |
249 publish.side_effect = Exception('uncaught') | |
250 with self.assertRaises(Exception): | |
251 mon.send(collection) | |
252 | |
253 def message(pb): | |
254 pb = monitors.Monitor._wrap_proto(pb) | |
255 return {'messages': [{'data': base64.b64encode(pb.SerializeToString())}]} | |
256 publish.assert_has_calls([ | |
257 mock.call(topic=topic, body=message(metric1)), | |
258 mock.call().execute(num_retries=5), | |
259 mock.call(topic=topic, body=message([metric1, metric2])), | |
260 mock.call(topic=topic, body=message(collection)), | |
261 ]) | |
262 | |
263 | |
264 | |
265 class DebugMonitorTest(unittest.TestCase): | |
266 | |
267 def test_send_file(self): | |
268 with infra_libs.temporary_directory() as temp_dir: | |
269 filename = os.path.join(temp_dir, 'out') | |
270 m = monitors.DebugMonitor(filename) | |
271 metric1 = metrics_pb2.MetricsData(name='m1') | |
272 m.send(metric1) | |
273 metric2 = metrics_pb2.MetricsData(name='m2') | |
274 m.send([metric1, metric2]) | |
275 collection = metrics_pb2.MetricsCollection(data=[metric1, metric2]) | |
276 m.send(collection) | |
277 with open(filename) as fh: | |
278 output = fh.read() | |
279 self.assertEquals(output.count('data {\n name: "m1"\n}'), 3) | |
280 self.assertEquals(output.count('data {\n name: "m2"\n}'), 2) | |
281 | |
282 def test_send_log(self): | |
283 m = monitors.DebugMonitor() | |
284 metric1 = metrics_pb2.MetricsData(name='m1') | |
285 m.send(metric1) | |
286 metric2 = metrics_pb2.MetricsData(name='m2') | |
287 m.send([metric1, metric2]) | |
288 collection = metrics_pb2.MetricsCollection(data=[metric1, metric2]) | |
289 m.send(collection) | |
290 | |
291 | |
292 class NullMonitorTest(unittest.TestCase): | |
293 | |
294 def test_send(self): | |
295 m = monitors.NullMonitor() | |
296 metric1 = metrics_pb2.MetricsData(name='m1') | |
297 m.send(metric1) | |
OLD | NEW |