• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 /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