• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import dbus
6import json
7import logging
8import os
9import shutil
10import tempfile
11import time
12
13from autotest_lib.client.cros import dbus_util
14from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import utils
16from autotest_lib.client.common_lib.cros.fake_device_server import \
17        fake_gcd_helper
18from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
19        commands
20from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
21        devices
22from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
23        fail_control
24from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
25        oauth
26from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
27        registration
28from autotest_lib.client.common_lib.cros.tendo import buffet_config
29from autotest_lib.client.common_lib.cros.tendo import buffet_dbus_helper
30
31
32TEST_NAME = 'test_name '
33TEST_DESCRIPTION = 'test_description '
34TEST_LOCATION = 'test_location '
35
36TEST_COMMAND_CATEGORY = 'registration_test'
37TEST_COMMAND_NAME = '_TestEcho'
38TEST_COMMAND_PARAM = 'message'
39TEST_COMMAND_DEFINITION = {
40    TEST_COMMAND_CATEGORY: {
41        TEST_COMMAND_NAME: {
42            'parameters': { TEST_COMMAND_PARAM: { 'type': 'string' } },
43            'results': {},
44            'name': 'Test Echo Command',
45        }
46    }
47}
48
49STATUS_UNCONFIGURED = 'unconfigured'
50STATUS_CONNECTING = 'connecting'
51STATUS_CONNECTED = 'connected'
52STATUS_INVALID_CREDENTIALS = 'invalid_credentials'
53
54def _assert_has(resource, key, value, resource_description):
55    if resource is None:
56        raise error.TestFail('Wanted %s[%s]=%r, but %s is None.' %
57                (resource_description, key, value))
58    if key not in resource:
59        raise error.TestFail('%s not in %s' % (key, resource_description))
60
61    if resource[key] != value:
62        raise error.TestFail('Wanted %s[%s]=%r, but got %r' %
63                (resource_description, key, value, resource[key]))
64
65
66class BuffetTester(object):
67    """Helper class for buffet tests."""
68
69
70    def __init__(self):
71        """Initialization routine."""
72        # We're going to confirm buffet is polling by issuing commands to
73        # the mock GCD server, then checking that buffet gets them.  The
74        # commands are test.TestEcho commands with a single parameter
75        # |message|.  |self._expected_messages| is a list of these messages.
76        self._expected_messages = []
77        # We store our command definitions under this root.
78        self._temp_dir_path = None
79        # Spin up our mock server.
80        self._gcd = fake_gcd_helper.FakeGCDHelper()
81        self._gcd.start()
82        # Create the command definition we want to use.
83        self._temp_dir_path = tempfile.mkdtemp()
84        commands_dir = os.path.join(self._temp_dir_path, 'commands')
85        os.mkdir(commands_dir)
86        command_definition_path = os.path.join(
87                commands_dir, '%s.json' % TEST_COMMAND_CATEGORY)
88        with open(command_definition_path, 'w') as f:
89            f.write(json.dumps(TEST_COMMAND_DEFINITION))
90        utils.run('chown -R buffet:buffet %s' % self._temp_dir_path)
91        logging.debug('Created test commands definition: %s',
92                      command_definition_path)
93        # Create client proxies for interacting with oyr fake server.
94        self._registration_client = registration.RegistrationClient(
95                server_url=buffet_config.LOCAL_SERVICE_URL,
96                api_key=buffet_config.TEST_API_KEY)
97        self._device_client = devices.DevicesClient(
98                server_url=buffet_config.LOCAL_SERVICE_URL,
99                api_key=buffet_config.TEST_API_KEY)
100        self._oauth_client = oauth.OAuthClient(
101                server_url=buffet_config.LOCAL_SERVICE_URL,
102                api_key=buffet_config.TEST_API_KEY)
103        self._fail_control_client = fail_control.FailControlClient(
104                server_url=buffet_config.LOCAL_SERVICE_URL,
105                api_key=buffet_config.TEST_API_KEY)
106        self._command_client = commands.CommandsClient(
107                server_url=buffet_config.LOCAL_SERVICE_URL,
108                api_key=buffet_config.TEST_API_KEY)
109        self._config = buffet_config.BuffetConfig(
110                log_verbosity=3,
111                test_definitions_dir=self._temp_dir_path)
112
113
114    def check_buffet_status_is(self, expected_status,
115                               expected_device_id='',
116                               timeout_seconds=0):
117        """Assert that buffet has the given registration status.
118
119        Optionally, a timeout can be specified to wait until the
120        status changes.
121
122        @param expected_device_id: device id created during registration.
123        @param expected_status: the status to wait for.
124        @param timeout_seconds: number of seconds to wait for status to change.
125
126        """
127        buffet = buffet_dbus_helper.BuffetDBusHelper()
128        start_time = time.time()
129        while True:
130            actual_status = buffet.status
131            actual_device_id = buffet.device_id
132            if (actual_status == expected_status and
133                actual_device_id == expected_device_id):
134                return
135            time_spent = time.time() - start_time
136            if time_spent > timeout_seconds:
137                if actual_status != expected_status:
138                    raise error.TestFail('Buffet should be %s, but is %s '
139                                         '(waited %.1f seconds).' %
140                                         (expected_status, actual_status,
141                                          time_spent))
142                if actual_device_id != expected_device_id:
143                    raise error.TestFail('Device ID  should be %s, but is %s '
144                                         '(waited %.1f seconds).' %
145                                         (expected_device_id, actual_device_id,
146                                          time_spent))
147            time.sleep(0.5)
148
149
150    def check_buffet_is_polling(self, device_id, timeout_seconds=30):
151        """Assert that buffet is polling for new commands.
152
153        @param device_id: string device id created during registration.
154        @param timeout_seconds: number of seconds to wait for polling
155                to start.
156
157        """
158        new_command_message = ('This is message %d' %
159                               len(self._expected_messages))
160        command_resource = {
161            'name': '%s.%s' % (TEST_COMMAND_CATEGORY, TEST_COMMAND_NAME),
162            'deviceId': device_id,
163            'parameters': {TEST_COMMAND_PARAM: new_command_message}
164        }
165        self._expected_messages.append(new_command_message)
166        self._command_client.create_command(device_id, command_resource)
167        # Confirm that the command eventually appears on buffet.
168        buffet = buffet_dbus_helper.BuffetDBusHelper()
169        polling_interval_seconds = 0.5
170        start_time = time.time()
171        while time.time() - start_time < timeout_seconds:
172            objects = dbus_util.dbus2primitive(
173                    buffet.object_manager.GetManagedObjects())
174            cmds = [interfaces[buffet_dbus_helper.COMMAND_INTERFACE]
175                    for path, interfaces in objects.iteritems()
176                    if buffet_dbus_helper.COMMAND_INTERFACE in interfaces]
177            messages = [cmd['Parameters'][TEST_COMMAND_PARAM] for cmd in cmds
178                        if (cmd['Name'] == '%s.%s' % (TEST_COMMAND_CATEGORY,
179                                                      TEST_COMMAND_NAME))]
180            # |cmds| is a list of property sets
181            if len(messages) != len(self._expected_messages):
182                # Still waiting for our pending command to show up.
183                time.sleep(polling_interval_seconds)
184                continue
185            logging.debug('Finally saw the right number of commands over '
186                          'DBus: %r', cmds)
187            if sorted(messages) != sorted(self._expected_messages):
188                raise error.TestFail(
189                        'Expected commands with messages=%r but got %r.' %
190                        (self._expected_messages, messages))
191            logging.info('Buffet has DBus proxies for commands with '
192                         'messages: %r', self._expected_messages)
193            return
194        raise error.TestFail('Timed out waiting for Buffet to expose '
195                             'pending commands with messages: %r' %
196                             self._expected_messages)
197
198
199    def register_with_server(self):
200        """Make buffet register with the cloud server.
201
202        This includes the whole registration flow and ends with buffet
203        obtained an access token for future interactions. The status
204        is guaranteed to be STATUS_CONNECTED when this
205        method returns.
206
207        @return string: the device_id obtained during registration.
208
209        """
210        ticket = self._registration_client.create_registration_ticket()
211        logging.info('Created ticket: %r', ticket)
212        buffet = buffet_dbus_helper.BuffetDBusHelper()
213        buffet.manager.UpdateDeviceInfo(dbus.String(TEST_NAME),
214                                        dbus.String(TEST_DESCRIPTION),
215                                        dbus.String(TEST_LOCATION))
216        device_id = dbus_util.dbus2primitive(
217                buffet.manager.RegisterDevice(dbus.String(ticket['id'])))
218        # Confirm that registration has populated some fields.
219        device_resource = self._device_client.get_device(device_id)
220        logging.debug('Got device resource=%r', device_resource)
221        _assert_has(device_resource, 'name', TEST_NAME,
222                    'device resource')
223        _assert_has(device_resource, 'modelManifestId', 'AATST',
224                    'device resource')
225        logging.info('Registration successful')
226        self.check_buffet_status_is(STATUS_CONNECTED,
227                                    expected_device_id=device_id,
228                                    timeout_seconds=5)
229        return device_id
230
231
232    def restart_buffet(self, reset_state):
233        """Function for restarting the buffet daemon.
234
235        @param reset_state: If True, all local buffet state will be deleted.
236        """
237        self._config.restart_with_config(clean_state=reset_state)
238
239
240    def close(self):
241        """Cleanup to be used when done with this instance."""
242        buffet_config.naive_restart()
243        self._gcd.close()
244        if self._temp_dir_path is not None:
245            shutil.rmtree(self._temp_dir_path, True)
246