| 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 |