Home | History | Annotate | Download | only in fake_device_server
      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 
      5 """Module contains a simple implementation of the devices RPC."""
      6 
      7 from cherrypy import tools
      8 import logging
      9 import time
     10 
     11 import common
     12 from fake_device_server import common_util
     13 from fake_device_server import resource_method
     14 from fake_device_server import server_errors
     15 
     16 
     17 # TODO(sosa): All access to this object should technically require auth. Create
     18 # setters/getters for the auth token for testing.
     19 
     20 DEVICES_PATH = 'devices'
     21 
     22 
     23 class Devices(resource_method.ResourceMethod):
     24     """A simple implementation of the device interface.
     25 
     26     A common workflow of using this API is:
     27 
     28     POST .../ # Creates a new device with id <id>.
     29     PATCH ..../<id> # Update device state.
     30     GET .../<id> # Get device state.
     31     DELETE .../<id> # Delete the device.
     32     """
     33 
     34     # Needed for cherrypy to expose this to requests.
     35     exposed = True
     36 
     37 
     38     def __init__(self, resource, commands_instance, oauth_instance,
     39                  fail_control_handler):
     40         """Initializes a registration ticket.
     41 
     42         @param resource: A resource delegate for storing devices.
     43         @param commands_instance: Instance of commands method class.
     44         @param oauth_instance: Instance of oauth class.
     45         @param fail_control_handler: Instance of FailControl.
     46         """
     47         super(Devices, self).__init__(resource)
     48         self.commands_instance = commands_instance
     49         self._oauth = oauth_instance
     50         self._fail_control_handler = fail_control_handler
     51 
     52 
     53     def _handle_state_patch(self, device_id, api_key, data):
     54         """Patch a device's state with the given update data.
     55 
     56         @param device_id: string device id to update.
     57         @param api_key: string api_key to support this resource delegate.
     58         @param data: json blob provided to patchState API.
     59 
     60         """
     61         # TODO(wiley) this.
     62 
     63 
     64     def _validate_device_resource(self, resource):
     65         # Verify required keys exist in the device draft.
     66         if not resource:
     67             raise server_errors.HTTPError(400, 'Empty device resource.')
     68 
     69         for key in ['name', 'channel']:
     70             if key not in resource:
     71                 raise server_errors.HTTPError(400, 'Must specify %s' % key)
     72 
     73         # Add server fields.
     74         resource['kind'] = 'clouddevices#device'
     75         current_time_ms = str(int(round(time.time() * 1000)))
     76         resource['creationTimeMs'] = current_time_ms
     77         resource['lastUpdateTimeMs'] = current_time_ms
     78         resource['lastSeenTimeMs'] = current_time_ms
     79 
     80 
     81     def create_device(self, api_key, device_config):
     82         """Creates a new device given the device_config.
     83 
     84         @param api_key: Api key for the application.
     85         @param device_config: Json dict for the device.
     86         @raises server_errors.HTTPError: if the config is missing a required key
     87         """
     88         logging.info('Creating device with api_key=%s and device_config=%r',
     89                      api_key, device_config)
     90         self._validate_device_resource(device_config)
     91         new_device = self.resource.update_data_val(None, api_key,
     92                                                    data_in=device_config)
     93         self.commands_instance.new_device(new_device['id'])
     94         return new_device
     95 
     96 
     97     @tools.json_out()
     98     def GET(self, *args, **kwargs):
     99         """GET .../(device_id) gets device info or lists all devices.
    100 
    101         Supports both the GET / LIST commands for devices. List lists all
    102         devices a user has access to, however, this implementation just returns
    103         all devices.
    104 
    105         Raises:
    106             server_errors.HTTPError if the device doesn't exist.
    107         """
    108         self._fail_control_handler.ensure_not_in_failure_mode()
    109         id, api_key, _ = common_util.parse_common_args(args, kwargs)
    110         if not api_key:
    111             access_token = common_util.get_access_token()
    112             api_key = self._oauth.get_api_key_from_access_token(access_token)
    113         if id:
    114             return self.resource.get_data_val(id, api_key)
    115         else:
    116             # Returns listing (ignores optional parameters).
    117             listing = {'kind': 'clouddevices#devicesListResponse'}
    118             listing['devices'] = self.resource.get_data_vals()
    119             return listing
    120 
    121 
    122     @tools.json_out()
    123     def POST(self, *args, **kwargs):
    124         """Handle POSTs for a device.
    125 
    126         Supported APIs include:
    127 
    128         POST /devices/<device-id>/patchState
    129 
    130         """
    131         self._fail_control_handler.ensure_not_in_failure_mode()
    132         args = list(args)
    133         device_id = args.pop(0) if args else None
    134         operation = args.pop(0) if args else None
    135         if device_id is None or operation != 'patchState':
    136             raise server_errors.HTTPError(400, 'Unsupported operation.')
    137         data = common_util.parse_serialized_json()
    138         access_token = common_util.get_access_token()
    139         api_key = self._oauth.get_api_key_from_access_token(access_token)
    140         self._handle_state_patch(device_id, api_key, data)
    141         return {'state': self.resource.get_data_val(device_id,
    142                                                     api_key)['state']}
    143 
    144 
    145     @tools.json_out()
    146     def PUT(self, *args, **kwargs):
    147         """Update an existing device using the incoming json data.
    148 
    149         On startup, devices make a request like:
    150 
    151         PUT http://<server-host>/devices/<device-id>
    152 
    153         {'channel': {'supportedType': 'xmpp'},
    154          'commandDefs': {},
    155          'description': 'test_description ',
    156          'displayName': 'test_display_name ',
    157          'id': '4471f7',
    158          'location': 'test_location ',
    159          'name': 'test_device_name',
    160          'state': {'base': {'firmwareVersion': '6771.0.2015_02_09_1429',
    161                             'isProximityTokenRequired': False,
    162                             'localDiscoveryEnabled': False,
    163                             'manufacturer': '',
    164                             'model': '',
    165                             'serialNumber': '',
    166                             'supportUrl': '',
    167                             'updateUrl': ''}}}
    168 
    169         This PUT has no API key, but comes with an OAUTH access token.
    170 
    171         """
    172         self._fail_control_handler.ensure_not_in_failure_mode()
    173         device_id, _, _ = common_util.parse_common_args(args, kwargs)
    174         access_token = common_util.get_access_token()
    175         if not access_token:
    176             raise server_errors.HTTPError(401, 'Access denied.')
    177         api_key = self._oauth.get_api_key_from_access_token(access_token)
    178         data = common_util.parse_serialized_json()
    179         self._validate_device_resource(data)
    180 
    181         logging.info('Updating device with id=%s and device_config=%r',
    182                      device_id, data)
    183         new_device = self.resource.update_data_val(device_id, api_key,
    184                                                    data_in=data)
    185         return data
    186 
    187 
    188     def DELETE(self, *args, **kwargs):
    189         """Deletes the given device.
    190 
    191         Format of this call is:
    192         DELETE .../device_id
    193 
    194         Raises:
    195             server_errors.HTTPError if the device doesn't exist.
    196         """
    197         self._fail_control_handler.ensure_not_in_failure_mode()
    198         id, api_key, _ = common_util.parse_common_args(args, kwargs)
    199         self.resource.del_data_val(id, api_key)
    200         self.commands_instance.remove_device(id)
    201