blob: d23aa9caaeec9a7e8ca880d9519e3aab0bc3403f [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module contains a simple implementation of the commands RPC."""
from cherrypy import tools
import logging
import common
from fake_device_server import common_util
from fake_device_server import constants
from fake_device_server import resource_method
from fake_device_server import server_errors
# TODO(sosa) Support upload method (and mediaPath parameter).
class Commands(resource_method.ResourceMethod):
"""A simple implementation of the commands interface."""
# Needed for cherrypy to expose this to requests.
exposed = True
# Roots of command resource representation that might contain commands.
_COMMAND_ROOTS = set(['base', 'aggregator', 'printer', 'storage'])
def __init__(self, resource):
"""Initializes a registration ticket.
@param resource: A resource delegate.
"""
super(Commands, self).__init__(resource)
# Maps devices to commands.
self.device_commands = dict()
def new_device(self, device_id, api_key):
"""Adds knowledge of a device with the given |device_id|.
This method should be called whenever a new device is created. It
populates an empty command dict for each device state.
@param device_id: Device id to add.
@param api_key: key for the application.
"""
self.device_commands[(device_id, api_key)] = {}
def remove_device(self, device_id, api_key):
"""Removes knowledge of the given device.
@param device_id: Device id to remove.
@param api_key: key for the application.
"""
del self.device_commands[(device_id, api_key)]
def create_command(self, api_key, device_id, command_config):
"""Creates, queues and returns a new command.
@param api_key: Api key for the application.
@param device_id: Device id of device to send command.
@param command_config: Json dict for command.
"""
if (device_id, api_key) not in self.device_commands.keys():
raise server_errors.HTTPError(400, 'Unknown device with id %s' %
device_id)
# We only need to verify that a command is specified (and only one
# command is specified).
many_command_error = 'Either no commands or multiple commands specified'
command_key_set = self._COMMAND_ROOTS.intersection(
set(command_config.keys()))
if len(command_key_set) != 1:
# If this isn't exactly 1, then either no commands are specified OR
# more than one command is specified.
raise server_errors.HTTPError(400, many_command_error)
# Tracks the path of the command in the command_config.
command_parts = [command_key_set.pop()]
command = command_config[command_parts[0]]
# All commands must have exactly one entry that is the command one-level
# down i.e. base.Reboot.*
if len(command.keys()) != 1:
raise server_errors.HTTPError(400, many_command_error)
command_parts.append(command.keys()[0])
# Print out something useful (command base.Reboot)
logging.info('Received command %s', '.'.join(command_parts))
# TODO(sosa): Check to see if command is in devices CDD.
# Queue command, create it and insert to device->command mapping.
command_config['state'] = constants.QUEUED_STATE
new_command = self.resource.update_data_val(None, api_key,
data_in=command_config)
self.device_commands[(device_id,
api_key)][new_command['id']] = new_command
return new_command
@tools.json_out()
def GET(self, *args, **kwargs):
"""GET .../(command_id) gets command info or lists all devices.
Supports both the GET / LIST commands for commands. List lists all
devices a user has access to, however, this implementation just returns
all devices.
Raises:
server_errors.HTTPError if the device doesn't exist.
"""
id, api_key, _ = common_util.parse_common_args(args, kwargs)
if id:
return self.resource.get_data_val(id, api_key)
else:
# Returns listing (ignores optional parameters).
listing = {'kind': 'clouddevices#commandsListResponse'}
device_id = kwargs.get('deviceId')
if not device_id:
raise server_errors.HTTPError(400, 'Can only list commands by '
'deviceId.')
requested_state = kwargs.get('state')
listing['commands'] = []
for command in self.device_commands[(device_id, api_key)].values():
# Check state for match (if None, just append all of them).
if not requested_state or requested_state == command['state']:
listing['commands'].append(command)
return listing
@tools.json_out()
def POST(self, *args, **kwargs):
"""Creates a new device using the incoming json data."""
id, api_key, _ = common_util.parse_common_args(args, kwargs)
data = common_util.parse_serialized_json()
if not data:
data = {}
device_id = kwargs.get('deviceId')
if not device_id:
raise server_errors.HTTPError(400, 'Can only create a command if '
'you provide a deviceId.')
return self.create_command(api_key, device_id, data)