Home | History | Annotate | Download | only in tendo
      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 dbus
      6 import json
      7 import logging
      8 import os
      9 import shutil
     10 import tempfile
     11 import time
     12 
     13 from autotest_lib.client.cros import dbus_util
     14 from autotest_lib.client.common_lib import error
     15 from autotest_lib.client.common_lib import utils
     16 from autotest_lib.client.common_lib.cros.fake_device_server import \
     17         fake_gcd_helper
     18 from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
     19         commands
     20 from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
     21         devices
     22 from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
     23         fail_control
     24 from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
     25         oauth
     26 from autotest_lib.client.common_lib.cros.fake_device_server.client_lib import \
     27         registration
     28 from autotest_lib.client.common_lib.cros.tendo import buffet_config
     29 from autotest_lib.client.common_lib.cros.tendo import buffet_dbus_helper
     30 
     31 
     32 TEST_NAME = 'test_name '
     33 TEST_DESCRIPTION = 'test_description '
     34 TEST_LOCATION = 'test_location '
     35 
     36 TEST_COMMAND_CATEGORY = 'registration_test'
     37 TEST_COMMAND_NAME = '_TestEcho'
     38 TEST_COMMAND_PARAM = 'message'
     39 TEST_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 
     49 STATUS_UNCONFIGURED = 'unconfigured'
     50 STATUS_CONNECTING = 'connecting'
     51 STATUS_CONNECTED = 'connected'
     52 STATUS_INVALID_CREDENTIALS = 'invalid_credentials'
     53 
     54 def _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 
     66 class 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