Tester feedback: client implementation.

This implements the test-side client library for interacting with
a human tester. Also instrumenting the client interface with sets
including input/output/all query types.

BUG=b:26514064
TEST=test_droid -i <dut-serial> brillo_OpenSLESPlaybackAudioTest

Change-Id: I0417e230532a1b00e3b5aa4078fd9e423fc80148
Reviewed-on: https://chromium-review.googlesource.com/324530
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
diff --git a/client/common_lib/feedback/client.py b/client/common_lib/feedback/client.py
index 4557140..9a0a4e3 100644
--- a/client/common_lib/feedback/client.py
+++ b/client/common_lib/feedback/client.py
@@ -29,6 +29,25 @@
 # Power management testing.
 QUERY_POWER_WAKEUP = 60
 
+INPUT_QUERIES = set((
+        QUERY_AUDIO_RECORDING,
+        QUERY_MOTION_RESTING,
+        QUERY_MOTION_MOVING,
+        QUERY_KEYBOARD_PLUG,
+        QUERY_KEYBOARD_TYPE,
+        QUERY_GPIO_READ,
+        QUERY_POWER_WAKEUP,
+))
+
+OUTPUT_QUERIES = set((
+        QUERY_AUDIO_PLAYBACK_SILENT,
+        QUERY_AUDIO_PLAYBACK_AUDIBLE,
+        QUERY_GPIO_WRITE,
+        QUERY_LIGHT_ON,
+))
+
+ALL_QUERIES = INPUT_QUERIES.union(OUTPUT_QUERIES)
+
 
 # Feedback client definition.
 #
diff --git a/client/common_lib/feedback/tester_feedback_client.py b/client/common_lib/feedback/tester_feedback_client.py
new file mode 100644
index 0000000..a870a6a
--- /dev/null
+++ b/client/common_lib/feedback/tester_feedback_client.py
@@ -0,0 +1,152 @@
+# Copyright 2016 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.
+
+"""Feedback client implementation for interacting with a human tester."""
+
+import xmlrpclib
+
+import common
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib.feedback import client
+
+
+# Query return codes.
+#
+QUERY_RET_SUCCESS = 0
+QUERY_RET_FAIL = 1
+QUERY_RET_ERROR = 2
+
+
+class Client(client.Client):
+    """Human tester feedback implementation."""
+
+    def __init__(self, test_name, dut_name, remote_addr):
+        """Constructs the client object.
+
+        @param test_name: The name of the test.
+        @param dut_name: The name of the DUT.
+        @param remote_addr: The 'name:port' of the remote feedback service host.
+        """
+        super(Client, self).__init__()
+        self._client_id = '%s:%s' % (test_name, dut_name)
+        self._remote_addr = remote_addr
+        self._query_num = 0
+        self._rpc_proxy = None
+
+
+    def _make_query_call(self, query_num, query_method, **kwargs):
+        """Make an RPC query call (used by query objects).
+
+        @param query_num: The unique query identifying number.
+        @param query_method: The query method being called.
+
+        @raise xmlrpclib.Error: An error during RPC call processing.
+        """
+        # XML-RPC does not support kwargs, so we just pass it as a dictionary.
+        return self._rpc_proxy.query_call(self._client_id, query_num,
+                                          query_method, kwargs)
+
+
+    # Interface overrides.
+    #
+    def _initialize_impl(self, _test, _host):
+        """Initializes the feedback object.
+
+        Initializes an XML-RPC proxy and registers the client at the remote end.
+
+        @param _test: An object representing the test case (unused).
+        @param _host: An object representing the DUT (unused).
+        """
+        self._rpc_proxy = xmlrpclib.ServerProxy('http://%s' % self._remote_addr)
+        try:
+            self._rpc_proxy.new_client(self._client_id)
+        except xmlrpclib.Error as e:
+            raise error.TestError('Feedback client registration error: %s' % e)
+
+
+    def _new_query_impl(self, query_id):
+        """Instantiates a new query.
+
+        @param query_id: A query identifier.
+
+        @return A query object.
+        """
+        if query_id in client.INPUT_QUERIES:
+            query_cls = InputQuery
+        elif query_id in client.OUTPUT_QUERIES:
+            query_cls = OutputQuery
+        else:
+            raise error.TestError('Unknown query (%s)' % query_id)
+
+        # Create, register and return a new query.
+        self._query_num += 1
+        try:
+            self._rpc_proxy.new_query(self._client_id, query_id, self._query_num)
+        except xmlrpclib.Error as e:
+            raise error.TestError('Feedback query registration error: %s' % e)
+        return query_cls(self, self._query_num)
+
+
+    def _finalize_impl(self):
+        """Finalizes the feedback object."""
+        try:
+            self._rpc_proxy.delete_client(self._client_id)
+        except xmlrpclib.Error as e:
+            raise error.TestError(
+                    'Feedback client deregistration error: %s' % e)
+
+
+class _Query(object):
+    """Human tester feedback query base class."""
+
+    def __init__(self, client, query_num):
+        super(_Query, self).__init__()
+        self.client = client
+        self.query_num = query_num
+
+
+    def _make_query_call(self, query_method, **kwargs):
+        try:
+            ret, desc = self.client._make_query_call(self.query_num,
+                                                     query_method, **kwargs)
+        except xmlrpclib.Error as e:
+            ret, desc = QUERY_RET_ERROR, str(e)
+
+        if ret == QUERY_RET_SUCCESS:
+            return
+        if ret == QUERY_RET_FAIL:
+            raise error.TestFail('Tester feedback request failed: %s' % desc)
+        if ret == QUERY_RET_ERROR:
+            raise error.TestError('Tester feedback request error: %s' % desc)
+        raise error.TestError('Unknown feedback call return code (%s)' % ret)
+
+
+    # Interface overrides.
+    #
+    def _prepare_impl(self, **kwargs):
+        self._make_query_call('prepare', **kwargs)
+
+
+    def _validate_impl(self, **kwargs):
+        self._make_query_call('validate', **kwargs)
+
+
+class OutputQuery(_Query, client.OutputQuery):
+    """Human tester feedback output query."""
+
+    def __init__(self, client, query_num):
+        super(OutputQuery, self).__init__(client, query_num)
+
+
+class InputQuery(_Query, client.InputQuery):
+    """Human tester feedback input query."""
+
+    def __init__(self, client, query_num):
+        super(InputQuery, self).__init__(client, query_num)
+
+
+    # Interface override.
+    #
+    def _emit_impl(self):
+        self._make_query_call('emit')