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"""Tester feedback delegate."""
6
7import logging
8import xmlrpclib
9
10import common
11from autotest_lib.client.common_lib.feedback import tester_feedback_client
12
13import query_delegate
14
15
16class FeedbackDelegate(object):
17    """An object for managing feedback RPC calls."""
18
19    def __init__(self, multiplexer):
20        self._multiplexer = multiplexer
21        self._clients = {}
22
23
24    def _get_client(self, client_id):
25        """Returns the query dictionary for a client.
26
27        @param client_id: The client identifier.
28
29        @return: A dictionary mapping registered query numbers to query delegate
30                 objects for the given client.
31
32        @raise xmlrpclib.Fault: The client was not registered.
33        """
34        if client_id not in self._clients:
35            raise xmlrpclib.Fault('Unknown client (%s)' % client_id)
36        return self._clients[client_id]
37
38
39    def _get_delegate_cls(self, query_id):
40        """Returns a query delegate class for a given query type.
41
42        @param query_id: The query type for which a delegate is needed.
43
44        @return: A query delegate class.
45
46        @raise xmlrpclib.Fault: Query type is invalid or unsupported.
47        """
48        try:
49            return query_delegate.get_delegate_cls(query_id)
50        except ValueError:
51            raise xmlrpclib.Fault('Unknown query type (%s)' % query_id)
52        except NotImplementedError:
53            raise xmlrpclib.Fault('Unsupported query type (%s)' % query_id)
54
55
56    def new_client(self, client_id):
57        """Register a new client.
58
59        A client identifier is unique for a given test and DUT: at any given
60        time, there's only one test that is using this identifier. That said,
61        a client identifier may be reused across different tests at different
62        times within the lifetime of the feedback delegate. In general, clients
63        are expected to unregister when they finish running. However, for the
64        delegate to be resilient to test crashes, we forgo this requirement and
65        only emit a warning.
66
67        @param client_id: The client identifier.
68
69        @return: True (avoiding None with XML-RPC).
70        """
71        if client_id in self._clients:
72            logging.warning('Overwriting existing client entry %s; prior '
73                            'instance did not shutdown properly?', client_id)
74        self._clients[client_id] = {}
75        return True
76
77
78    def delete_client(self, client_id):
79        """Unregister a client.
80
81        @param client_id: The client identifier.
82
83        @return: True (avoiding None with XML-RPC).
84        """
85        del self._clients[client_id]
86        return True
87
88
89    def new_query(self, client_id, query_id, query_num):
90        """Register a new query from a client.
91
92        @param client_id: The client identifier.
93        @param query_id: The query type.
94        @param query_num: The query's unique number.
95
96        @return: True (avoiding None with XML-RPC).
97
98        @raise xmlrpclib.Fault: The client or query arguments are invalid.
99        """
100        client = self._get_client(client_id)
101        if query_num in client:
102            raise xmlrpclib.Fault('New query (%s) is already registered' %
103                                  query_num)
104        test_name, dut_name = client_id.split(':')
105        client[query_num] = self._get_delegate_cls(query_id)(
106                test_name, dut_name, self._multiplexer)
107        return True
108
109
110    def query_call(self, client_id, query_num, query_method, kwargs_dict):
111        """Perform a query call.
112
113        @param client_id: The client identifier.
114        @param query_num: The query unique number.
115        @param query_method: The method being called.
116        @param kwargs_dict: Extra arguments being passed to the method call.
117
118        @return: A pair containing a method return code (constant defined in
119                 tester_feedback_client) and a description of the result
120                 (string).
121
122        @raise: xmlrpclib.Fault: Method execution failed.
123        """
124        try:
125            query = self._get_client(client_id)[query_num]
126        except KeyError:
127            raise xmlrpclib.Fault('Query %d unknown to client %s' %
128                                  (query_num, client_id))
129
130        # Route the query call to the appropriate method.
131        local_method = getattr(query, query_method, None)
132        if local_method is None:
133            ret = (tester_feedback_client.QUERY_RET_ERROR,
134                   'Unknown query method (%s)' % query_method)
135        else:
136            ret = local_method(**kwargs_dict)
137
138        # If there's an explicit result, return it; otherwise, return success.
139        if ret is None:
140            return tester_feedback_client.QUERY_RET_SUCCESS, ''
141        return ret
142