1# Copyright 2016 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"""Feedback client implementation for interacting with a human tester."""
6
7import xmlrpclib
8
9import common
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib.feedback import client
12
13
14# Query return codes.
15#
16QUERY_RET_SUCCESS = 0
17QUERY_RET_FAIL = 1
18QUERY_RET_ERROR = 2
19
20
21class Client(client.Client):
22    """Human tester feedback implementation."""
23
24    def __init__(self, test_name, dut_name, remote_addr):
25        """Constructs the client object.
26
27        @param test_name: The name of the test.
28        @param dut_name: The name of the DUT.
29        @param remote_addr: The 'name:port' of the remote feedback service host.
30        """
31        super(Client, self).__init__()
32        self._client_id = '%s:%s' % (test_name, dut_name)
33        self._remote_addr = remote_addr
34        self._query_num = 0
35        self._rpc_proxy = None
36
37
38    def _make_query_call(self, query_num, query_method, **kwargs):
39        """Make an RPC query call (used by query objects).
40
41        @param query_num: The unique query identifying number.
42        @param query_method: The query method being called.
43
44        @raise xmlrpclib.Error: An error during RPC call processing.
45        """
46        # XML-RPC does not support kwargs, so we just pass it as a dictionary.
47        return self._rpc_proxy.query_call(self._client_id, query_num,
48                                          query_method, kwargs)
49
50
51    # Interface overrides.
52    #
53    def _initialize_impl(self, _test, _host):
54        """Initializes the feedback object.
55
56        Initializes an XML-RPC proxy and registers the client at the remote end.
57
58        @param _test: An object representing the test case (unused).
59        @param _host: An object representing the DUT (unused).
60        """
61        self._rpc_proxy = xmlrpclib.ServerProxy('http://%s' % self._remote_addr)
62        try:
63            self._rpc_proxy.new_client(self._client_id)
64        except xmlrpclib.Error as e:
65            raise error.TestError('Feedback client registration error: %s' % e)
66
67
68    def _new_query_impl(self, query_id):
69        """Instantiates a new query.
70
71        @param query_id: A query identifier.
72
73        @return A query object.
74        """
75        if query_id in client.INPUT_QUERIES:
76            query_cls = InputQuery
77        elif query_id in client.OUTPUT_QUERIES:
78            query_cls = OutputQuery
79        else:
80            raise error.TestError('Unknown query (%s)' % query_id)
81
82        # Create, register and return a new query.
83        self._query_num += 1
84        try:
85            self._rpc_proxy.new_query(self._client_id, query_id, self._query_num)
86        except xmlrpclib.Error as e:
87            raise error.TestError('Feedback query registration error: %s' % e)
88        return query_cls(self, self._query_num)
89
90
91    def _finalize_impl(self):
92        """Finalizes the feedback object."""
93        try:
94            self._rpc_proxy.delete_client(self._client_id)
95        except xmlrpclib.Error as e:
96            raise error.TestError(
97                    'Feedback client deregistration error: %s' % e)
98
99
100class _Query(object):
101    """Human tester feedback query base class."""
102
103    def __init__(self, client, query_num):
104        super(_Query, self).__init__()
105        self.client = client
106        self.query_num = query_num
107
108
109    def _make_query_call(self, query_method, **kwargs):
110        try:
111            ret, desc = self.client._make_query_call(self.query_num,
112                                                     query_method, **kwargs)
113        except xmlrpclib.Error as e:
114            ret, desc = QUERY_RET_ERROR, str(e)
115
116        if ret == QUERY_RET_SUCCESS:
117            return
118        if ret == QUERY_RET_FAIL:
119            raise error.TestFail('Tester feedback request failed: %s' % desc)
120        if ret == QUERY_RET_ERROR:
121            raise error.TestError('Tester feedback request error: %s' % desc)
122        raise error.TestError('Unknown feedback call return code (%s)' % ret)
123
124
125    # Interface overrides.
126    #
127    def _prepare_impl(self, **kwargs):
128        self._make_query_call('prepare', **kwargs)
129
130
131    def _validate_impl(self, **kwargs):
132        self._make_query_call('validate', **kwargs)
133
134
135class OutputQuery(_Query, client.OutputQuery):
136    """Human tester feedback output query."""
137
138    def __init__(self, client, query_num):
139        super(OutputQuery, self).__init__(client, query_num)
140
141
142class InputQuery(_Query, client.InputQuery):
143    """Human tester feedback input query."""
144
145    def __init__(self, client, query_num):
146        super(InputQuery, self).__init__(client, query_num)
147
148
149    # Interface override.
150    #
151    def _emit_impl(self):
152        self._make_query_call('emit')
153