• 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
5"""Module contains a simple implementation of the commands RPC."""
6
7from cherrypy import tools
8import logging
9import uuid
10
11import common
12from fake_device_server import common_util
13from fake_device_server import constants
14from fake_device_server import server_errors
15
16COMMANDS_PATH = 'commands'
17
18
19# TODO(sosa) Support upload method (and mediaPath parameter).
20class Commands(object):
21    """A simple implementation of the commands interface."""
22
23    # Needed for cherrypy to expose this to requests.
24    exposed = True
25
26    # Roots of command resource representation that might contain commands.
27    _COMMAND_ROOTS = set(['base', 'aggregator', 'printer', 'storage', 'test'])
28
29
30    def __init__(self, oauth_handler, fail_control_handler):
31        """Initializes a Commands handler."""
32        # A map of device_id's to maps of command ids to command resources
33        self.device_commands = dict()
34        self._num_commands_created = 0
35        self._oauth_handler = oauth_handler
36        self._fail_control_handler = fail_control_handler
37
38
39    def _generate_command_id(self):
40        """@return unique command ID."""
41        command_id = '%s_%03d' % (uuid.uuid4().hex[0:6],
42                                  self._num_commands_created)
43        self._num_commands_created += 1
44        return command_id
45
46    def new_device(self, device_id):
47        """Adds knowledge of a device with the given |device_id|.
48
49        This method should be called whenever a new device is created. It
50        populates an empty command dict for each device state.
51
52        @param device_id: Device id to add.
53
54        """
55        self.device_commands[device_id] = {}
56
57
58    def remove_device(self, device_id):
59        """Removes knowledge of the given device.
60
61        @param device_id: Device id to remove.
62
63        """
64        del self.device_commands[device_id]
65
66
67    def create_command(self, command_resource):
68        """Creates, queues and returns a new command.
69
70        @param api_key: Api key for the application.
71        @param device_id: Device id of device to send command.
72        @param command_resource: Json dict for command.
73        """
74        device_id = command_resource.get('deviceId', None)
75        if not device_id:
76            raise server_errors.HTTPError(
77                    400, 'Can only create a command if you provide a deviceId.')
78
79        if device_id not in self.device_commands:
80            raise server_errors.HTTPError(
81                    400, 'Unknown device with id %s' % device_id)
82
83        if 'name' not in command_resource:
84            raise server_errors.HTTPError(
85                    400, 'Missing command name.')
86
87        # Print out something useful (command base.Reboot)
88        logging.info('Received command %s', command_resource['name'])
89
90        # TODO(sosa): Check to see if command is in devices CDD.
91        # Queue command, create it and insert to device->command mapping.
92        command_id = self._generate_command_id()
93        command_resource['id'] = command_id
94        command_resource['state'] = constants.QUEUED_STATE
95        self.device_commands[device_id][command_id] = command_resource
96        return command_resource
97
98
99    @tools.json_out()
100    def GET(self, *args, **kwargs):
101        """Handle GETs against the command API.
102
103        GET .../(command_id) returns a command resource
104        GET .../queue?deviceId=... returns the command queue
105        GET .../?deviceId=... returns the command queue
106
107        Supports both the GET / LIST commands for commands. List lists all
108        devices a user has access to, however, this implementation just returns
109        all devices.
110
111        Raises:
112            server_errors.HTTPError if the device doesn't exist.
113
114        """
115        self._fail_control_handler.ensure_not_in_failure_mode()
116        args = list(args)
117        requested_command_id = args.pop(0) if args else None
118        device_id = kwargs.get('deviceId', None)
119        if args:
120            raise server_errors.HTTPError(400, 'Unsupported API')
121        if not device_id or device_id not in self.device_commands:
122            raise server_errors.HTTPError(
123                    400, 'Can only list commands by valid deviceId.')
124        if requested_command_id is None:
125            requested_command_id = 'queue'
126
127        if not self._oauth_handler.is_request_authorized():
128            raise server_errors.HTTPError(401, 'Access denied.')
129
130        if requested_command_id == 'queue':
131            # Returns listing (ignores optional parameters).
132            listing = {'kind': 'clouddevices#commandsListResponse'}
133            requested_state = kwargs.get('state', None)
134            listing['commands'] = []
135            for _, command in self.device_commands[device_id].iteritems():
136                # Check state for match (if None, just append all of them).
137                if (requested_state is None or
138                        requested_state == command['state']):
139                    listing['commands'].append(command)
140            logging.info('Returning queue of commands: %r', listing)
141            return listing
142
143        for command_id, resource in self.device_commands[device_id].iteritems():
144            if command_id == requested_command_id:
145                return self.device_commands[device_id][command_id]
146
147        raise server_errors.HTTPError(
148                400, 'No command with ID=%s found' % requested_command_id)
149
150
151    @tools.json_out()
152    def POST(self, *args, **kwargs):
153        """Creates a new command using the incoming json data."""
154        # TODO(wiley) We could check authorization here, which should be
155        #             a client/owner of the device.
156        self._fail_control_handler.ensure_not_in_failure_mode()
157        data = common_util.parse_serialized_json()
158        if not data:
159            raise server_errors.HTTPError(400, 'Require JSON body')
160
161        return self.create_command(data)
162