1# Copyright 2014 The Chromium OS 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 5import logging 6import os 7import shutil 8import SimpleHTTPServer 9import sys 10import threading 11 12from autotest_lib.client.bin import test 13from autotest_lib.client.common_lib import autotemp, error, file_utils, utils 14from autotest_lib.client.cros import httpd, service_stopper 15 16 17SERVER_PORT=51793 18SERVER_ADDRESS = "http://localhost:%s/uma/v2" % SERVER_PORT 19 20class FakeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 21 """ 22 Fake Uma handler. 23 24 Answer OK on well formed request and add the data to the server's list of 25 messages. 26 """ 27 28 def do_POST(self): 29 """ 30 Handle post request to the fake UMA backend. 31 32 Answer 'OK' with a 200 HTTP status code on POST requests to /uma/v2 33 and an empty message with error code 404 otherwise. 34 """ 35 if self.path != '/uma/v2': 36 self.send_response(404) 37 self.end_headers() 38 return 39 40 message = self.rfile.read(int(self.headers['Content-Length'])) 41 self.server.messages.append(message) 42 43 self.send_response(200) 44 self.send_header('Content-type', 'text/html') 45 self.end_headers() 46 self.wfile.write('OK') 47 48 49class FakeServer(httpd.ThreadedHTTPServer): 50 """ 51 Wrapper around ThreadedHTTPServer. 52 53 Provides helpers to start/stop the instance and hold the list of 54 received messages. 55 """ 56 57 def __init__(self): 58 httpd.ThreadedHTTPServer.__init__(self, ('', SERVER_PORT), FakeHandler) 59 self.messages = [] 60 61 62 def Start(self): 63 """ 64 Start the server on a new thread. 65 """ 66 self.server_thread = threading.Thread(target=self.serve_forever) 67 self.server_thread.start() 68 69 70 def Stop(self): 71 """ 72 Stop the server thread. 73 """ 74 self.shutdown() 75 self.socket.close() 76 self.server_thread.join() 77 78 79class platform_MetricsUploader(test.test): 80 """ 81 End-to-End test of the metrics uploader 82 83 Test that metrics_daemon is sending the metrics to the Uma server when 84 started with the -uploader flag and that the messages are well formatted. 85 """ 86 87 version = 1 88 _CONSENT_FILE = '/home/chronos/Consent To Send Stats' 89 90 def setup(self): 91 os.chdir(self.srcdir) 92 utils.make('OUT_DIR=.') 93 94 95 def initialize(self): 96 self._services = service_stopper.ServiceStopper(['metrics_daemon']) 97 self._services.stop_services() 98 self._tempdir = autotemp.tempdir() 99 100 101 def _create_one_sample(self): 102 utils.system_output('truncate --size=0 /var/run/metrics/uma-events') 103 utils.system_output('metrics_client test 10 0 100 10') 104 105 106 def _test_simple_upload(self): 107 self._create_one_sample() 108 109 self.server = FakeServer() 110 self.server.Start() 111 112 utils.system_output('metrics_daemon -uploader_test ' 113 '-server="%s"' % SERVER_ADDRESS, 114 timeout=10, retain_output=True) 115 116 self.server.Stop() 117 118 if len(self.server.messages) != 1: 119 raise error.TestFail('no messages received by the server') 120 121 122 def _test_server_unavailable(self): 123 """ 124 metrics_daemon should not crash when the server is unavailable. 125 """ 126 self._create_one_sample() 127 utils.system_output('metrics_daemon -uploader_test ' 128 '-server="http://localhost:12345"', 129 retain_output=True) 130 131 132 def _test_check_product_id(self): 133 """ 134 metrics_daemon should set the product id when it is specified. 135 136 The product id can be set through the GOOGLE_METRICS_PRODUCT_ID file in 137 /etc/os-release.d/. 138 """ 139 140 # The product id must be an integer, declared in the upstream UMA 141 # backend protobuf. 142 EXPECTED_PRODUCT_ID = 3 143 144 sys.path.append(self.srcdir) 145 from chrome_user_metrics_extension_pb2 import ChromeUserMetricsExtension 146 147 self._create_one_sample() 148 149 self.server = FakeServer() 150 self.server.Start() 151 osreleased_path = os.path.join(self._tempdir.name, 'etc', 152 'os-release.d') 153 file_utils.make_leaf_dir(osreleased_path) 154 utils.write_one_line(os.path.join(osreleased_path, 155 'GOOGLE_METRICS_PRODUCT_ID'), 156 str(EXPECTED_PRODUCT_ID)) 157 158 utils.system_output('metrics_daemon -uploader_test ' 159 '-server="%s" ' 160 '-config_root="%s"' % (SERVER_ADDRESS, 161 self._tempdir.name), 162 retain_output=True) 163 164 self.server.Stop() 165 166 if len(self.server.messages) != 1: 167 raise error.TestFail('should have received 1 message. Received: ' 168 + str(len(self.server.messages))) 169 170 proto = ChromeUserMetricsExtension.FromString(self.server.messages[0]) 171 logging.debug('Proto received is: ' + str(proto)) 172 if proto.product != EXPECTED_PRODUCT_ID: 173 raise error.TestFail('Product id should be set to 3. Was: ' 174 + str(proto.product)) 175 176 177 def _test_metrics_disabled(self): 178 """ 179 When metrics are disabled, nothing should get uploaded. 180 """ 181 self._create_one_sample() 182 183 self.server = FakeServer() 184 self.server.Start() 185 186 utils.system_output('metrics_daemon -uploader_test ' 187 '-server="%s"' % SERVER_ADDRESS, 188 timeout=10, retain_output=True) 189 190 self.server.Stop() 191 192 if len(self.server.messages) != 0: 193 raise error.TestFail('message received by the server') 194 195 196 def _get_saved_consent_file_path(self): 197 return os.path.join(self.bindir, 'saved_consent') 198 199 200 def run_once(self): 201 """ 202 Run the tests. 203 """ 204 if os.path.exists(self._CONSENT_FILE): 205 shutil.move(self._CONSENT_FILE, self._get_saved_consent_file_path()) 206 # enable metrics reporting 207 utils.open_write_close(self._CONSENT_FILE, 'foo') 208 209 logging.info(('=' * 4) + 'Check that metrics samples can be uploaded ' 210 'with the default configuration') 211 self._test_simple_upload() 212 213 logging.info(('=' * 4) + 'Check that the metrics uploader does not ' 214 'crash when the backend server is unreachable') 215 self._test_server_unavailable() 216 217 logging.info(('=' * 4) + 'Check that the product id can be set ' 218 'through the GOOGLE_METRICS_PRODUCT_ID field in ' 219 '/etc/os-release.d/') 220 self._test_check_product_id() 221 222 os.remove(self._CONSENT_FILE) 223 logging.info(('=' * 4) + 'Check that metrics are not uploaded when ' 224 'metrics are disabled.') 225 self._test_metrics_disabled() 226 227 228 def cleanup(self): 229 self._services.restore_services() 230 self._tempdir.clean() 231 232 # The consent file might or might not exist depending on whether a test 233 # failed or not. Handle both cases. 234 if os.path.exists(self._CONSENT_FILE): 235 os.remove(self._CONSENT_FILE) 236 237 if os.path.exists(self._get_saved_consent_file_path()): 238 shutil.move(self._get_saved_consent_file_path(), self._CONSENT_FILE) 239